/*!
    \copyright  Copyright (c) 2023-2024 Qualcomm Technologies International, Ltd.
                All Rights Reserved.
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \version    
    \file       google_finder_advertiser.c
    \ingroup    google_finder
    \brief      Component handling Google Finder Advertisement. Multiple data sets concurrency 
                of LE advertising MUST BE enabled.
*/

#ifdef INCLUDE_GOOGLE_FINDER
#ifdef INCLUDE_LEGACY_LE_ADVERTISING_MANAGER
#error Multiple data sets concurrency of LE advertising manager MUST BE enabled to handle Google Finder Advertisement
#endif /* INCLUDE_LEGACY_LE_ADVERTISING_MANAGER */
#ifndef INCLUDE_LEGACY_LE_ADVERTISING_MANAGER
#include <panic.h>
#include <string.h>
#include <stdio.h>
#include <logging.h>

#include "google_finder_advertiser.h"
#include "google_finder.h"
#include "google_finder_pddu.h"
#include "google_finder_private.h"
#include "device_properties.h"
#include "device_db_serialiser.h"
#include "cryptoalgo.h"
#include "state_of_charge.h"
#include "bt_device.h"
#include "fast_pair_config.h"
#include "fast_pair_msg_stream.h"
#include "fast_pair_msg_stream_dev_info.h"
#include "fast_pair_msg_stream_dev_action.h"

#ifdef FIND_MY_DEVICE_ENABLE_DULT
#include "dult.h"
#endif

/*! Global Instance of Google Finder Advertising Data */
google_finder_advert_data_t google_finder_advert;
uint8 google_finder_adv_packet[GOOGLE_FINDER_ADV_P160_DATA_PACKET_SIZE] = {0};

static le_adv_item_data_t google_finder_data_item;

static void googleFinderAdv_UpdateBatteryState(void)
{
    uint8 battery_percentage = Soc_GetBatterySoc();

    if (battery_percentage == 0)
    {
        DEBUG_LOG("googleFinderAdv_UpdateBatteryState, battery SOC not supported");
    }

    GOOGLE_FINDER_FLAGS_RESET_BATTERY_STATE(google_finder_advert.raw_flags);

    if (battery_percentage >= GOOGLE_FINDER_BATTERY_NORMAL_PERCENTAGE_MIN_THRESHOLD)
    {
        GOOGLE_FINDER_FLAGS_SET_BATTERY_STATE(google_finder_advert.raw_flags, 
                                              GOOGLE_FINDER_ADV_HASHED_FLAGS_NORMAL_BATTERY);
    }
    else if (battery_percentage < GOOGLE_FINDER_BATTERY_NORMAL_PERCENTAGE_MIN_THRESHOLD && 
             battery_percentage >= GOOGLE_FINDER_BATTERY_LOW_PERCENTAGE_MIN_THRESHOLD)
    {
        GOOGLE_FINDER_FLAGS_SET_BATTERY_STATE(google_finder_advert.raw_flags, 
                                              GOOGLE_FINDER_ADV_HASHED_FLAGS_LOW_BATTERY);
    }
    else
    {
        GOOGLE_FINDER_FLAGS_SET_BATTERY_STATE(google_finder_advert.raw_flags, 
                                              GOOGLE_FINDER_ADV_HASHED_FLAGS_CRITICAL_BATTERY);
    }

    DEBUG_LOG("googleFinderAdv_UpdateBatteryState 0x%x",
                GOOGLE_FINDER_FLAGS_GET_BATTERY_STATE(google_finder_advert.raw_flags));
}

/*! \brief Prepare the Google Finder Advertisement Data.
    \return none
*/
static void googleFinderAdv_PrepareAdvData(void)
{
    if(google_finder_advert.adv_data_state != GOOGLE_FINDER_ADV_GENERATED || 
       google_finder_advert.public_key_adv_data.public_key == NULL)
    {
        google_finder_data_item.size = GOOGLE_FINDER_ADV_INVALID_DATA_PACKET_SIZE;
        google_finder_data_item.data = NULL;

        DEBUG_LOG("googleFinderAdv_PrepareAdvData. Data unavailable");
        return;
    }

    DEBUG_LOG("googleFinderAdv_PrepareAdvData. Data Size - %d", GOOGLE_FINDER_ADV_P160_DATA_PACKET_SIZE);

    google_finder_adv_packet[0] = GOOGLE_FINDER_ADV_STANDARD_DATA_LEN;
    google_finder_adv_packet[1] = ble_ad_type_flags;
    google_finder_adv_packet[2] = (BLE_FLAGS_GENERAL_DISCOVERABLE_MODE | BLE_FLAGS_SINGLE_MODE);
    google_finder_adv_packet[3] = GOOGLE_FINDER_ADV_SERVICE_DATA_LEN;
    google_finder_adv_packet[4] = ble_ad_type_service_data;
    google_finder_adv_packet[5] = GOOGLE_FINDER_ADV_EDDYSTONE_IDENTIFIER & 0xFF;
    google_finder_adv_packet[6] = (GOOGLE_FINDER_ADV_EDDYSTONE_IDENTIFIER >> 8) & 0xFF;

    if(GoogleFinder_IsUnwantedTrackingProtModeEnabled())
    {
        google_finder_adv_packet[7] = GOOGLE_FINDER_ADV_E2EE_EID_FRAME_TYPE_IN_UTP_MODE;
    }
    else
    {
        google_finder_adv_packet[7] = GOOGLE_FINDER_ADV_E2EE_EID_FRAME_TYPE;
    }

    /* Offset starting from 8 to 27 will be the Ephemeral Key identifier */
    memcpy(&google_finder_adv_packet[GOOGLE_FINDER_ADV_EPHEMERAL_IDENTIFIER_OFFSET], 
            google_finder_advert.public_key_adv_data.public_key, 
            GOOGLE_FINDER_ADV_EPHEMERAL_IDENTIFIER_LEN);

    /* Get latest battery state */
    googleFinderAdv_UpdateBatteryState();

    google_finder_adv_packet[GOOGLE_FINDER_ADV_HASHED_FLAGS_OFFSET] = (google_finder_advert.raw_flags ^ google_finder_advert.private_key_hash);

    google_finder_data_item.size = GOOGLE_FINDER_ADV_P160_DATA_PACKET_SIZE;

    google_finder_data_item.data = google_finder_adv_packet;

    /* Send current EID and clock value to all the connected seekers as ADV data has been changed now */
    GoogleFinder_SendEIDAndClockValue(FASTPAIR_SEEKER_INVALID);
}

/*! \brief Notify the LE Advertisement Manager for data change in the advertisement.
    \param none
    \return bool status of the operation.
*/
static bool googleFinderAdv_NotifyDataChange(void)
{
    bool status = FALSE;

    DEBUG_LOG("googleFinderAdv_NotifyDataChange");

    if (google_finder_advert.adv_register_handle)
    {
        status = LeAdvertisingManager_UpdateAdvertisingItem(google_finder_advert.adv_register_handle);
    }
    else
    {
        DEBUG_LOG("Invalid handle in googleFinderAdv_NotifyDataChange");
    }

    return status;
}

/*! \brief Get the advertisement packet size for Google Finder Advertisement
    \param none
    \return uint8 Google Finder Adverts packet size
*/
static uint8 googleFinderAdv_GetAdvDataSize(void)
{
    if(google_finder_advert.adverts_enabled &&
       google_finder_advert.adv_data_state == GOOGLE_FINDER_ADV_GENERATED)
    {
        return GOOGLE_FINDER_ADV_P160_DATA_PACKET_SIZE;
    }
    else
    {
        return GOOGLE_FINDER_ADV_INVALID_DATA_PACKET_SIZE;
    }
}

static bool googleFinderAdv_GetItemData(le_adv_item_data_t * data)
{
    DEBUG_LOG("googleFinderAdv_GetItemData");

    PanicNull(data);
    data->data = NULL;
    data->size = 0;

    *data = google_finder_data_item;
    return TRUE;
}

static void googleFinderAdv_ReleaseItemData(void)
{
    DEBUG_LOG("googleFinderAdv_ReleaseItemData");

    if(google_finder_advert.public_key_adv_data.public_key != NULL)
    {
        free(google_finder_advert.public_key_adv_data.public_key);
        google_finder_advert.public_key_adv_data.public_key = NULL;
    }
}

static bool googleFinderAdv_GetItemInfo(le_adv_item_info_t * info)
{
    DEBUG_LOG("googleFinderAdv_GetItemInfo");

    PanicNull(info);
    *info = (le_adv_item_info_t){ .placement = le_adv_item_data_placement_advert,
                                  .type = le_adv_type_legacy_connectable_scannable,
                                  .data_size = googleFinderAdv_GetAdvDataSize(),
                                  .dont_include_flags = TRUE};
    return TRUE;
}

static bool googleFinderAdv_GetItemParams(le_adv_item_params_t * params)
{
    DEBUG_LOG("googleFinderAdv_GetItemParams");

    PanicNull(params);

    if (GOOGLE_FINDER_FLAGS_IS_UNWANTED_TRACKING_PROTECTION_MODE_SET(google_finder_advert.raw_flags))
    {
        params->random_addr_type = ble_local_addr_write_resolvable;

        params->random_addr.type = TYPED_BDADDR_RANDOM;
        params->random_addr.addr = google_finder_advert.adv_bdaddr;

        params->random_addr_generate_rotation_timeout_minimum_in_minutes = 0;
        params->random_addr_generate_rotation_timeout_maximum_in_minutes = 0;

        DEBUG_LOG("googleFinderAdv_GetItemParams in UTP set addr %04x,%02x,%06lx",
                    params->random_addr.addr.nap, params->random_addr.addr.uap, params->random_addr.addr.lap);
    }
    else
    {
        params->random_addr_type = ble_local_addr_generate_resolvable;
        params->random_addr_generate_rotation_timeout_minimum_in_minutes = GOOGLE_FINDER_ADV_RPA_ROTATION_TIME_MIN;
        params->random_addr_generate_rotation_timeout_maximum_in_minutes = GOOGLE_FINDER_ADV_RPA_ROTATION_TIME_MAX;
    }
    params->own_addr_type = OWN_ADDRESS_RANDOM;

    params->primary_adv_interval_min = GOOGLE_FINDER_ADV_INTERVAL_MIN;
    params->primary_adv_interval_max = GOOGLE_FINDER_ADV_INTERVAL_MAX;

    return TRUE;
}

static void googleFinderAdv_HandleAdvertisingEventNotified(le_adv_event_t event, const void * event_data)
{
    UNUSED(event_data);

    DEBUG_LOG("googleFinderAdv_HandleAdvertisingEventNotified event ID enum:le_adv_event_t:%d", event);

    if (event == LEAM_EVENT_ADVERTISING_SET_RANDOM_ADDRESS_CHANGED)
    {
        const LEAM_EVENT_ADVERTISING_SET_RANDOM_ADDRESS_CHANGED_T * data = 
                (const LEAM_EVENT_ADVERTISING_SET_RANDOM_ADDRESS_CHANGED_T *)event_data;

        /* Ignore the first RPA changed event */
        if (!BdaddrIsZero(&google_finder_advert.adv_bdaddr) &&
            !BdaddrIsSame(&google_finder_advert.adv_bdaddr, &data->new_bdaddr))
        {
            /* RPA changed, new adv data should be picked up */
            google_finder_advert.random_address_rotation_timeout_event_notified = TRUE;
        }

        /* Update the adverts if the new public key has already been generated */
        if(google_finder_advert.is_generated_public_key_unused)
        {
            googleFinderAdv_HandleUpdatedEid();
        }

#ifdef FIND_MY_DEVICE_ENABLE_DULT
        Dult_SetAdvAddress(data->new_bdaddr);
#endif
        DEBUG_LOG("googleFinderAdv_HandleAdvertisingEventNotified: new addr LAP: 0x%06lx", data->new_bdaddr.lap);

        google_finder_advert.adv_bdaddr = data->new_bdaddr;
    }
}

/*! Callback registered with LE Advertising Manager*/
static const le_adv_item_callback_t google_finder_advertising_callback = {
    .GetItemData = &googleFinderAdv_GetItemData,
    .ReleaseItemData = &googleFinderAdv_ReleaseItemData,
    .GetItemInfo = &googleFinderAdv_GetItemInfo,
    .GetItemParameters = &googleFinderAdv_GetItemParams,
    .NotifyAdvertisingEvent = &googleFinderAdv_HandleAdvertisingEventNotified
};

static void googleFinderAdv_HandleGenerateAesEcb256Cfm(CL_CRYPTO_ENCRYPT_AES_ECB_256_CFM_T *cfm)
{
    DEBUG_LOG("googleFinderAdv_HandleGenerateAesEcb256Cfm. Status: %d, ADV state: %d", cfm->status, google_finder_advert.adv_data_state);

    if(google_finder_advert.adv_data_state == GOOGLE_FINDER_ADV_GENERATE_AES_ECB_256_STATE)
    {
        if(cfm->status == success)
        {
            googleFinderTaskData *google_finder_task_ptr = googleFinder_GetTaskData();

            DEBUG_LOG("googleFinderAdv_HandleGenerateAesEcb256Cfm. AES ECB 256 Output:");
            DEBUG_LOG_DATA_DEBUG(cfm->encr_data, GOOGLE_FINDER_ADV_AES_ECB_256_OUTPUT_SIZE);

            ConnectionGenerateEccP160PrivateKey(&google_finder_task_ptr->task, cfm->encr_data, GOOGLE_FINDER_ADV_AES_ECB_256_OUTPUT_SIZE);

            google_finder_advert.adv_data_state = GOOGLE_FINDER_ADV_GENERATE_PRIVATE_KEY_STATE;
        }
        else
        {
            DEBUG_LOG("googleFinderAdv_HandleGenerateAesEcb256Cfm. Unexpected error: %d ADV state: %d", cfm->error, google_finder_advert.adv_data_state);

            /* Move the adv state to IDLE */
            google_finder_advert.adv_data_state = GOOGLE_FINDER_ADV_IDLE_STATE;
        }
    }
}

/* Helper function to hash private key, copies LSB byte of output to supplied "hash" pointer */
static void googleFinder_calculatePrivateKeyHash(uint8 *private_key, uint8 *hash)
{
    uint8 sha256_ip[GOOGLE_FINDER_ADV_P160_PRIVATE_KEY_SIZE];
    uint8 sha256_out[SHA256_DIGEST_SIZE];

    memcpy(&sha256_ip[0], private_key, GOOGLE_FINDER_ADV_P160_PRIVATE_KEY_SIZE);

    sha256(sha256_ip, GOOGLE_FINDER_ADV_P160_PRIVATE_KEY_SIZE, sha256_out);

    *hash = sha256_out[SHA256_DIGEST_SIZE-1];
}

static void googleFinderAdv_HandleGeneratePrivateKeyCfm(CL_CRYPTO_GENERATE_ECC_P160_PRIVATE_KEY_CFM_T*cfm)
{
    DEBUG_LOG("googleFinderAdv_HandleGeneratePrivateKeyCfm. Status: %d, ADV state: %d", cfm->status, google_finder_advert.adv_data_state);

    if(google_finder_advert.adv_data_state == GOOGLE_FINDER_ADV_GENERATE_PRIVATE_KEY_STATE)
    {
        if(cfm->status == success)
        {
            googleFinderTaskData *google_finder_task_ptr = googleFinder_GetTaskData();

            /* Hash 20 byte private key to generate hashed data, as of now only
             * 1 octet LSB of hash is used for calculation 
             */
            googleFinder_calculatePrivateKeyHash(&cfm->private_key[1], &google_finder_advert.private_key_hash);

            DEBUG_LOG("googleFinderAdv_HandleGeneratePrivateKeyCfm. LSB Hash 0x%x Private Key:", google_finder_advert.private_key_hash);
            DEBUG_LOG_DATA_DEBUG(cfm->private_key, (GOOGLE_FINDER_ADV_P160_PRIVATE_KEY_SIZE + 1));

            ConnectionGenerateEccP160PublicKey(&google_finder_task_ptr->task, cfm->private_key, TRUE);

            google_finder_advert.adv_data_state = GOOGLE_FINDER_ADV_GENERATE_PUBLIC_KEY_STATE;
        }
        else
        {
            DEBUG_LOG("googleFinderAdv_HandleGeneratePrivateKeyCfm. Unexpected error: %d ADV state: %d", cfm->error, google_finder_advert.adv_data_state);

            /* Move the adv state to IDLE */
            google_finder_advert.adv_data_state = GOOGLE_FINDER_ADV_IDLE_STATE;
        }
    }
}

static void googleFinderAdv_HandleGeneratePublicKeyCfm(CL_CRYPTO_GENERATE_ECC_P160_PUBLIC_KEY_CFM_T *cfm)
{
    DEBUG_LOG("googleFinderAdv_HandleGeneratePublicKeyCfm. Status: %d, ADV state: %d", cfm->status, google_finder_advert.adv_data_state);

    if(google_finder_advert.adv_data_state == GOOGLE_FINDER_ADV_GENERATE_PUBLIC_KEY_STATE)
    {
        if(cfm->status == success)
        {
            google_finder_advert.adv_data_state = GOOGLE_FINDER_ADV_GENERATED;

            if (google_finder_advert.public_key_adv_data.public_key != NULL)
            {
                free(google_finder_advert.public_key_adv_data.public_key);
                google_finder_advert.public_key_adv_data.public_key = NULL;
            }

            /* This memory gets freed upon Release Item Call */
            google_finder_advert.public_key_adv_data.public_key = (uint8 *)PanicUnlessMalloc(GOOGLE_FINDER_ADV_P160_COMPRESSED_PUBLIC_KEY_SIZE);

            /* Actual public key starts from second byte, first byte is the prefix */
            memcpy(google_finder_advert.public_key_adv_data.public_key, (cfm->public_key + 1), GOOGLE_FINDER_ADV_P160_COMPRESSED_PUBLIC_KEY_SIZE);

            DEBUG_LOG("googleFinderAdv_HandleGeneratePublicKeyCfm. Public Key:");
            DEBUG_LOG_DATA_DEBUG(google_finder_advert.public_key_adv_data.public_key, GOOGLE_FINDER_ADV_P160_COMPRESSED_PUBLIC_KEY_SIZE);

            /*
             * Update the adverts only if this is the first time or RPA timeout event has been notified OR
             * EIK has been updated.
             */
            if(!google_finder_advert.adverts_enabled || google_finder_advert.random_address_rotation_timeout_event_notified || 
                google_finder_advert.eik_updated_pending_eid_change)
            {
                googleFinderAdv_HandleUpdatedEid();
            }
            /* Advertisement is already enabled and now a new public key got generated, but RPA timeout has not happened yet. */
            else
            {
                google_finder_advert.is_generated_public_key_unused = TRUE;
            }

            free(cfm->public_key);
        }
        else
        {
            DEBUG_LOG("googleFinderAdv_HandleGeneratePublicKeyCfm. Unexpected error: %d ADV state: %d", cfm->error, google_finder_advert.adv_data_state);
        }
    }
}

/*! \brief Initialization of Google Finder Global.
    \param none
    \return none
*/
static void googleFinderAdv_InitializeGlobal(void)
{
    memset(&google_finder_advert, 0, sizeof(google_finder_advert_data_t));
    google_finder_advert.adv_register_handle = NULL;
    google_finder_advert.public_key_adv_data.public_key = NULL;
    google_finder_advert.adv_data_state = GOOGLE_FINDER_ADV_IDLE_STATE;
    google_finder_advert.adverts_enabled = FALSE;
    google_finder_advert.random_address_rotation_timeout_event_notified = FALSE;
    google_finder_advert.is_generated_public_key_unused = FALSE;
    google_finder_advert.eik_updated_pending_eid_change =FALSE;
    googleFinderAdv_UpdateBatteryState();
}

void googleFinderAdv_UpdateHashedFlagsAdvData(void)
{
    if(google_finder_advert.adv_data_state != GOOGLE_FINDER_ADV_GENERATED || 
       !google_finder_advert.adverts_enabled)
    {
        DEBUG_LOG("googleFinderAdv_UpdateHashedFlagsAdvData.Error data unavailable");
        return;
    }

    DEBUG_LOG("googleFinderAdv_UpdateHashedFlagsAdvData, raw_flags 0x%x", google_finder_advert.raw_flags);

    /* Get latest battery state */
    googleFinderAdv_UpdateBatteryState();

    google_finder_adv_packet[GOOGLE_FINDER_ADV_HASHED_FLAGS_OFFSET] = (google_finder_advert.raw_flags ^ google_finder_advert.private_key_hash);

    if(GoogleFinder_IsUnwantedTrackingProtModeEnabled())
    {
        google_finder_adv_packet[GOOGLE_FINDER_ADV_EID_FRAME_TYPE_OFFSET] = GOOGLE_FINDER_ADV_E2EE_EID_FRAME_TYPE_IN_UTP_MODE;
    }

    /* Call update to change adv data and advertising parameter  */
    googleFinderAdv_NotifyDataChange();
}

void googleFinderAdv_KickAdvDataChange(void)
{
    googleFinderTaskData *google_finder_task_ptr = googleFinder_GetTaskData();
    bool update_finder_adverts = FALSE;
    bool is_tracking_disabled = GoogleFinder_IsLocationTrackingDisabled();
    uint32 previous_beacon_time_counter;
    uint32 current_beacon_time_counter;

    DEBUG_LOG("googleFinderAdv_KickAdvDataChange");

    /* Store the beacon time counter when MSB bits are changing */
    previous_beacon_time_counter = googleFinder_GetBeaconTimeCounter() & GOOGLE_FINDER_ADV_MASK_TIME_COUNTER_VALUE;
    current_beacon_time_counter = google_finder_task_ptr->beacon_time_data.beacon_time & GOOGLE_FINDER_ADV_MASK_TIME_COUNTER_VALUE;

    if(current_beacon_time_counter > previous_beacon_time_counter)
    {
        /*
         * If the time counter used for previous public key is matching with the current time counter, no need to update the adverts.
         * This can happen when we have already generated a new public key for next time counter window when RPA timeout event was notified.
         */
        if(current_beacon_time_counter != google_finder_advert.public_key_adv_data.time_counter_used_for_public_key)
        {
            update_finder_adverts = TRUE;
        }
        googleFinder_PersistBeaconTimeCounter(google_finder_task_ptr->beacon_time_data.beacon_time);
    }

    /* Stop eddystone advertisement when location tracking is disabled */
    if(google_finder_advert.adverts_enabled && is_tracking_disabled)
    {
       googleFinderAdv_StopAdvertisement();
    }

    /*
     * Google Finder advertising data should change only when current beacon time counter's
     * MSB bits changes. Construct the Eddystone Frame if MSB bits are changing or it's the first handle
     * after beacon provisioning process or after reboot OR eik has changed. Eddystone Frame will not 
     * be constructed when location tracking is disabled
     */
    if(!is_tracking_disabled)
    {
        if((!google_finder_advert.adverts_enabled || update_finder_adverts || 
             google_finder_advert.eik_updated_pending_eid_change))
        {
            /* 
            * if current beacon time counter is not near to next MSB update, then costruct the eddystone frame now, 
            * otherwise wait for time counter MSB update.
            */
            if(!GOOGLE_FINDER_IS_CURRENT_TIME_NEARING_MSB_UPDATE(google_finder_task_ptr->beacon_time_data.beacon_time))
            {
                googleFinderAdv_ConstructEddystoneFrame(FALSE);
            }
            else
            {
                DEBUG_LOG("googleFinderAdv_KickAdvDataChange. Construct Eddystone Frame when MSB update happens.");
            }
        }
        /* Force construct the next eddystone frame for next time counter MSB bit change if RPA timeout has happened */
        else if(google_finder_advert.random_address_rotation_timeout_event_notified)
        {
            /* 
             * If the time counter used for currently advertising public key is same as the next time counter MSB update
             * then reset random_address_rotation_timeout_event_notified to avoid constructing the same eddystone frame again.
             */
            if(google_finder_advert.public_key_adv_data.time_counter_used_for_public_key == 
               GOOGLE_FINDER_GET_NEXT_TIME_COUNTER_FOR_EID_UPDATE(google_finder_task_ptr->beacon_time_data.beacon_time))
            {
                google_finder_advert.random_address_rotation_timeout_event_notified = FALSE;

                DEBUG_LOG("googleFinderAdv_KickAdvDataChange. Already advertising the EID from next window. Continue with same EID");

                /* 
                 * Notify LEAM to advertise the EID data which was suspended
                 * upon LEAM_EVENT_ADVERTISING_SET_SUSPENDED event.
                 */
                googleFinderAdv_NotifyDataChange();
            }
            else
            {
                googleFinderAdv_ConstructEddystoneFrame(TRUE);
            }
        }
    }
}

void googleFinderAdv_ConstructEddystoneFrame(bool force_construct_eddystone_frame)
{
    uint8 *key = googleFinder_GetEIK();

    if (google_finder_advert.adv_data_state != GOOGLE_FINDER_ADV_GENERATED &&
        google_finder_advert.adv_data_state != GOOGLE_FINDER_ADV_IDLE_STATE)
    {
        DEBUG_LOG("googleFinderAdv_ConstructEddystoneFrame.Error, Generation already in progress!");
        return;
    }

    if(key != NULL)
    {
        uint8 *raw_eddystone_frame = (uint8 *)PanicUnlessMalloc(GOOGLE_FINDER_ADV_RAW_EDDYSTONE_FRAME_LEN);
        googleFinderTaskData *google_finder_task_ptr = googleFinder_GetTaskData();
        uint32 time_counter = google_finder_task_ptr->beacon_time_data.beacon_time;
        uint8 index;

        if(force_construct_eddystone_frame)
        {
            DEBUG_LOG("googleFinderAdv_ConstructEddystoneFrame. Forced to generate the eddystone frame.");

            time_counter = GOOGLE_FINDER_GET_NEXT_TIME_COUNTER_FOR_EID_UPDATE(time_counter);
        }

        /*
         * Store the time counter value which will be used to compare with the current time counter when 
         * the actual MSB bit changes or next time counter window when RPA timeout event received upon 
         * GATT connection/disconnection to avoid generating the same public key again.
         */
        google_finder_advert.public_key_adv_data.time_counter_used_for_public_key = time_counter & GOOGLE_FINDER_ADV_MASK_TIME_COUNTER_VALUE;

        DEBUG_LOG("googleFinderAdv_ConstructEddystoneFrame. Construct Eddystone Frame");

        /* Prepare the raw eddystone frame in the below format:
           0 - 10   Padding Value = 0xFF
           11       K Rotation period exponent
           12 - 15  TS[0]...TS[3] Beacon time counter, in 32-bit big-endian format. The K (i.e. 10) lowest bits are cleared. Mask with 0xFFFFFC00
           16 - 26  Padding Value = 0x00
           27       K Rotation period exponent
           28 - 31  TS[0]...TS[3] Beacon time counter, in 32-bit big-endian format. The K lowest bits are cleared.
        */
        for(index = 0; index < GOOGLE_FINDER_ADV_PADDING_FIELD_LEN; index++)
        {
            raw_eddystone_frame[GOOGLE_FINDER_ADV_FIRST_PADDING_OFFSET + index] = GOOGLE_FINDER_ADV_FIRST_PADDING_VALUE;
        }

        raw_eddystone_frame[GOOGLE_FINDER_ADV_FIRST_ROTATION_PERIOD_EXPONENT_OFFSET] = GOOGLE_FINDER_ADV_ROTATION_PERIOD_EXPONENT_VALUE;

        /* As mentioned above, the lowest 10 bits are cleared, so need to mask with 11111111 11111111 11111100 00000000 i.e. 0xFFFFFC00 */
        raw_eddystone_frame[GOOGLE_FINDER_ADV_FIRST_TIME_COUNTER_OFFSET + 3] = (uint8)(time_counter & 0x00);
        raw_eddystone_frame[GOOGLE_FINDER_ADV_FIRST_TIME_COUNTER_OFFSET + 2] = (uint8)((time_counter >> 8) & 0xFC);
        raw_eddystone_frame[GOOGLE_FINDER_ADV_FIRST_TIME_COUNTER_OFFSET + 1] = (uint8)((time_counter >> 16) & 0xFF);
        raw_eddystone_frame[GOOGLE_FINDER_ADV_FIRST_TIME_COUNTER_OFFSET + 0] = (uint8)((time_counter >> 24) & 0xFF);

        for(index = 0; index < GOOGLE_FINDER_ADV_PADDING_FIELD_LEN; index++)
        {
            raw_eddystone_frame[GOOGLE_FINDER_ADV_SECOND_PADDING_OFFSET + index] = GOOGLE_FINDER_ADV_SECOND_PADDING_VALUE;
        }

        raw_eddystone_frame[GOOGLE_FINDER_ADV_SECOND_ROTATION_PERIOD_EXPONENT_OFFSET] = GOOGLE_FINDER_ADV_ROTATION_PERIOD_EXPONENT_VALUE;

        raw_eddystone_frame[GOOGLE_FINDER_ADV_SECOND_TIME_COUNTER_OFFSET + 3] = (uint8)(time_counter & 0x00);
        raw_eddystone_frame[GOOGLE_FINDER_ADV_SECOND_TIME_COUNTER_OFFSET + 2] = (uint8)((time_counter >> 8) & 0xFC);
        raw_eddystone_frame[GOOGLE_FINDER_ADV_SECOND_TIME_COUNTER_OFFSET + 1] = (uint8)((time_counter >> 16) & 0xFF);
        raw_eddystone_frame[GOOGLE_FINDER_ADV_SECOND_TIME_COUNTER_OFFSET + 0] = (uint8)((time_counter >> 24) & 0xFF);

        ConnectionEncryptBlockAesEcb256(&google_finder_task_ptr->task, raw_eddystone_frame, GOOGLE_FINDER_ADV_RAW_EDDYSTONE_FRAME_LEN, key);

        google_finder_advert.adv_data_state = GOOGLE_FINDER_ADV_GENERATE_AES_ECB_256_STATE;

        free(key);
    }
    else
    {
        DEBUG_LOG("googleFinderAdv_ConstructEddystoneFrame. Unexpected Error - EIK is NULL");
    }
}

void googleFinderAdv_StopAdvertisement(void)
{
    googleFinderAdv_ReleaseItemData();
    googleFinderAdv_PrepareAdvData();
    google_finder_advert.adverts_enabled = FALSE;

    if(googleFinderAdv_NotifyDataChange())
    {
        DEBUG_LOG("googleFinderAdv_StopAdvertisement. Stopping Finder Adverts.");
        /* Disable unwanted location tracking.
        Tracking remain enabled another 24 hours */
#ifdef FIND_MY_DEVICE_ENABLE_DULT
        Dult_Disable();
#endif
    }
    else
    {
        DEBUG_LOG("googleFinderAdv_StopAdvertisement. Unable to stop the adverts.");
    }
}

void googleFinderAdv_StartAdvertisement(void)
{
    googleFinderAdv_KickAdvDataChange();

#ifdef FIND_MY_DEVICE_ENABLE_DULT
    Dult_Enable();
#endif

    DEBUG_LOG("googleFinderAdv_StartAdvertisement. Starting Finder Adverts.");
}

void googleFinderAdv_HandleCryptoMessage(MessageId id, Message message)
{
    switch(id)
    {
        case CL_CRYPTO_ENCRYPT_AES_ECB_256_CFM:
            googleFinderAdv_HandleGenerateAesEcb256Cfm((CL_CRYPTO_ENCRYPT_AES_ECB_256_CFM_T *)message);
            break;

        case CL_CRYPTO_GENERATE_ECC_P160_PRIVATE_KEY_CFM:
            googleFinderAdv_HandleGeneratePrivateKeyCfm((CL_CRYPTO_GENERATE_ECC_P160_PRIVATE_KEY_CFM_T *)message);
            break;

        case CL_CRYPTO_GENERATE_ECC_P160_PUBLIC_KEY_CFM:
            googleFinderAdv_HandleGeneratePublicKeyCfm((CL_CRYPTO_GENERATE_ECC_P160_PUBLIC_KEY_CFM_T *)message);
            break;

        default:
            DEBUG_LOG("googleFinderAdv_HandleCryptoMessage. Unhandled msg ID : %02x ADV State: %d", id, google_finder_advert.adv_data_state);
            break;
    }
}

void GoogleFinder_SendEIDAndClockValue(uint8 seeker_number)
{
    googleFinderTaskData *google_finder_task_ptr = googleFinder_GetTaskData();
    uint32 time_counter = google_finder_task_ptr->beacon_time_data.beacon_time;

    if(google_finder_advert.adv_data_state == GOOGLE_FINDER_ADV_GENERATED && time_counter != 0)
    {
        uint16 add_data_len = GOOGLE_FINDER_ADV_TIME_COUNTER_FIELD_LEN + GOOGLE_FINDER_ADV_EPHEMERAL_IDENTIFIER_LEN;
        uint8 *add_data = (uint8 *)PanicUnlessMalloc(add_data_len);

        DEBUG_LOG("GoogleFinder_SendEIDAndClockValue. Seeker Number: %d", seeker_number);

        /* Copy the time counter in big endian format */
        add_data[GOOGLE_FINDER_FP_MSG_CURR_EDDYSTONE_ID_CLOCK_OFFSET] = (uint8)((time_counter >> 24) & 0xFF);
        add_data[GOOGLE_FINDER_FP_MSG_CURR_EDDYSTONE_ID_CLOCK_OFFSET + 1] = (uint8)((time_counter >> 16) & 0xFF);
        add_data[GOOGLE_FINDER_FP_MSG_CURR_EDDYSTONE_ID_CLOCK_OFFSET + 2] = (uint8)((time_counter >> 8) & 0xFF);
        add_data[GOOGLE_FINDER_FP_MSG_CURR_EDDYSTONE_ID_CLOCK_OFFSET + 3] = (uint8)(time_counter & 0xFF);

        memcpy(&add_data[GOOGLE_FINDER_FP_MSG_CURR_EDDYSTONE_ID_EID_OFFSET], 
               &google_finder_adv_packet[GOOGLE_FINDER_ADV_EPHEMERAL_IDENTIFIER_OFFSET], 
               GOOGLE_FINDER_ADV_EPHEMERAL_IDENTIFIER_LEN);

        if(seeker_number == FASTPAIR_SEEKER_INVALID)
        {
            fastPair_MsgStreamSendDataToAll(FASTPAIR_MESSAGESTREAM_MESSAGE_GROUP_DEVICE_INFORMATION_EVENT,
                                            FASTPAIR_MESSAGESTREAM_DEVINFO_EVENT_CODE_EDDYSTONE_IDENTIFIER,
                                            add_data,
                                            add_data_len
                                            );
        }
        else
        {
            fastPair_MsgStreamSendData(FASTPAIR_MESSAGESTREAM_MESSAGE_GROUP_DEVICE_INFORMATION_EVENT,
                                       FASTPAIR_MESSAGESTREAM_DEVINFO_EVENT_CODE_EDDYSTONE_IDENTIFIER,
                                       add_data,
                                       add_data_len,
                                       seeker_number
                                       );
        }

        free(add_data);
    }
    else
    {
        DEBUG_LOG("GoogleFinder_SendEIDAndClockValue. Invalid EID data or Device is not provisioned yet.");
    }
}

void googleFinderAdv_HandleUpdatedEid(void)
{
    google_finder_advert.adverts_enabled = TRUE;

    /* Reset common adv flags now */
    google_finder_advert.random_address_rotation_timeout_event_notified = FALSE;
    google_finder_advert.is_generated_public_key_unused = FALSE;
    google_finder_advert.eik_updated_pending_eid_change = FALSE;

    googleFinderAdv_PrepareAdvData();
    googleFinderAdv_NotifyDataChange();

#ifdef FIND_MY_DEVICE_ENABLE_DULT
    Dult_Enable();
#endif

    /* EID Calculation has been completed, process the deferred indication */
    googleFinder_ProcessDeferredBeaconAction();
}

void googleFinderAdv_SetUpAdvertising(void)
{
    googleFinderTaskData *google_finder_task_ptr = googleFinder_GetTaskData();

    googleFinderAdv_InitializeGlobal();

    /*Register callback with Advertising Manager*/
    google_finder_advert.adv_register_handle = LeAdvertisingManager_RegisterAdvertisingItemCallback(&google_finder_task_ptr->task, &google_finder_advertising_callback);
}
#endif /* INCLUDE_LEGACY_LE_ADVERTISING_MANAGER */
#endif /* INCLUDE_GOOGLE_FINDER */
